Migrated HumanTask, Trigger and PeakDetectorAgent to Liquid

Made LiquidInterpolatable#interpolate_options recursive
Really interpolate the `message` options attribute of the PeakDetectorAgent
Merged all previous liquid migrations into one new migration
Added support for the `Agent#make_message` format to the liquid migrator

Dominik Sander 10 years ago
parent
commit
56324c87e2

+ 11 - 8
app/concerns/liquid_interpolatable.rb

@@ -2,16 +2,19 @@ module LiquidInterpolatable
2 2
   extend ActiveSupport::Concern
3 3
 
4 4
   def interpolate_options(options, payload)
5
-    duped_options = options.dup.tap do |duped_options|
6
-      duped_options.each_pair do |key, value|
7
-        if value.class == String
8
-          duped_options[key] = Liquid::Template.parse(value).render(payload)
9
-        else
10
-          duped_options[key] = value
11
-        end
5
+    case options.class.to_s
6
+    when 'String'
7
+      Liquid::Template.parse(options).render(payload)
8
+    when 'ActiveSupport::HashWithIndifferentAccess', 'Hash'
9
+      duped_options = options.dup
10
+      duped_options.each do |key, value|
11
+        duped_options[key] = interpolate_options(value, payload)
12
+      end
13
+    when 'Array'
14
+      options.collect do |value|
15
+        interpolate_options(value, payload)
12 16
       end
13 17
     end
14
-    duped_options
15 18
   end
16 19
 
17 20
   def interpolate_string(string, payload)

+ 10 - 8
app/models/agents/human_task_agent.rb

@@ -2,6 +2,8 @@ require 'rturk'
2 2
 
3 3
 module Agents
4 4
   class HumanTaskAgent < Agent
5
+    include LiquidInterpolatable
6
+
5 7
     default_schedule "every_10m"
6 8
 
7 9
     description <<-MD
@@ -16,7 +18,7 @@ module Agents
16 18
 
17 19
       # Example
18 20
 
19
-      If created with an event, all HIT fields can contain interpolated values via [JSONPaths](http://goessner.net/articles/JsonPath/) placed between < and > characters.
21
+      If created with an event, all HIT fields can contain interpolated values via [liquid templating](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid).
20 22
       For example, if the incoming event was a Twitter event, you could make a HITT to rate its sentiment like this:
21 23
 
22 24
           {
@@ -25,7 +27,7 @@ module Agents
25 27
             "hit": {
26 28
               "assignments": 1,
27 29
               "title": "Sentiment evaluation",
28
-              "description": "Please rate the sentiment of this message: '<$.message>'",
30
+              "description": "Please rate the sentiment of this message: '{{message}}'",
29 31
               "reward": 0.05,
30 32
               "lifetime_in_seconds": "3600",
31 33
               "questions": [
@@ -83,7 +85,7 @@ module Agents
83 85
               "title": "Take a poll about some jokes",
84 86
               "instructions": "Please rank these jokes from most funny (5) to least funny (1)",
85 87
               "assignments": 3,
86
-              "row_template": "<$.joke>"
88
+              "row_template": "{{joke}}"
87 89
             },
88 90
             "hit": {
89 91
               "assignments": 5,
@@ -168,7 +170,7 @@ module Agents
168 170
           {
169 171
             'assignments' => 1,
170 172
             'title' => "Sentiment evaluation",
171
-            'description' => "Please rate the sentiment of this message: '<$.message>'",
173
+            'description' => "Please rate the sentiment of this message: '{{message}}'",
172 174
             'reward' => 0.05,
173 175
             'lifetime_in_seconds' => 24 * 60 * 60,
174 176
             'questions' =>
@@ -332,7 +334,7 @@ module Agents
332 334
                   'name' => "Item #{index + 1}",
333 335
                   'key' => index,
334 336
                   'required' => "true",
335
-                  'question' => Utils.interpolate_jsonpaths(options['poll_options']['row_template'], assignments[index].answers),
337
+                  'question' => interpolate_string(options['poll_options']['row_template'], assignments[index].answers),
336 338
                   'selections' => selections
337 339
                 }
338 340
               end
@@ -387,9 +389,9 @@ module Agents
387 389
 
388 390
     def create_hit(opts = {})
389 391
       payload = opts['payload'] || {}
390
-      title = Utils.interpolate_jsonpaths(opts['title'], payload).strip
391
-      description = Utils.interpolate_jsonpaths(opts['description'], payload).strip
392
-      questions = Utils.recursively_interpolate_jsonpaths(opts['questions'], payload)
392
+      title = interpolate_string(opts['title'], payload).strip
393
+      description = interpolate_string(opts['description'], payload).strip
394
+      questions = interpolate_options(opts['questions'], payload)
393 395
       hit = RTurk::Hit.create(:title => title) do |hit|
394 396
         hit.max_assignments = (opts['assignments'] || 1).to_i
395 397
         hit.description = description

+ 4 - 2
app/models/agents/peak_detector_agent.rb

@@ -2,10 +2,12 @@ require 'pp'
2 2
 
3 3
 module Agents
4 4
   class PeakDetectorAgent < Agent
5
+    include LiquidInterpolatable
6
+
5 7
     cannot_be_scheduled!
6 8
 
7 9
     description <<-MD
8
-      Use a PeakDetectorAgent to watch for peaks in an event stream.  When a peak is detected, the resulting Event will have a payload message of `message`.  You can include extractions in the message, for example: `I saw a bar of: <foo.bar>`
10
+      Use a PeakDetectorAgent to watch for peaks in an event stream.  When a peak is detected, the resulting Event will have a payload message of `message`.  You can include extractions in the message, for example: `I saw a bar of: {{foo.bar}}`, have a look at the [Wiki](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) for details.
9 11
 
10 12
       The `value_path` value is a [JSONPaths](http://goessner.net/articles/JsonPath/) to the value of interest.  `group_by_path` is a hash path that will be used to group values, if present.
11 13
 
@@ -67,7 +69,7 @@ module Agents
67 69
         if newest_value > average_value + std_multiple * standard_deviation
68 70
           memory['peaks'][group] << newest_time
69 71
           memory['peaks'][group].reject! { |p| p <= newest_time - window_duration }
70
-          create_event :payload => { 'message' => options['message'], 'peak' => newest_value, 'peak_time' => newest_time, 'grouped_by' => group.to_s }
72
+          create_event :payload => { 'message' => interpolate_string(options['message'], event.payload), 'peak' => newest_value, 'peak_time' => newest_time, 'grouped_by' => group.to_s }
71 73
         end
72 74
       end
73 75
     end

+ 6 - 4
app/models/agents/trigger_agent.rb

@@ -1,5 +1,7 @@
1 1
 module Agents
2 2
   class TriggerAgent < Agent
3
+    include LiquidInterpolatable
4
+
3 5
     cannot_be_scheduled!
4 6
 
5 7
     VALID_COMPARISON_TYPES = %w[regex !regex field<value field<=value field==value field!=value field>=value field>value]
@@ -13,7 +15,7 @@ module Agents
13 15
 
14 16
       The `value` can be a single value or an array of values. In the case of an array, if one or more values match then the rule matches. 
15 17
 
16
-      All rules must match for the Agent to match.  The resulting Event will have a payload message of `message`.  You can include extractions in the message, for example: `I saw a bar of: <foo.bar>`
18
+      All rules must match for the Agent to match.  The resulting Event will have a payload message of `message`.  You can use liquid templating in the `message, have a look at the [Wiki](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) for details.
17 19
 
18 20
       Set `keep_event` to `true` if you'd like to re-emit the incoming event, optionally merged with 'message' when provided.
19 21
 
@@ -46,7 +48,7 @@ module Agents
46 48
                       'value' => "foo\\d+bar",
47 49
                       'path' => "topkey.subkey.subkey.goal",
48 50
                     }],
49
-        'message' => "Looks like your pattern matched in '<value>'!"
51
+        'message' => "Looks like your pattern matched in '{{value}}'!"
50 52
       }
51 53
     end
52 54
 
@@ -88,9 +90,9 @@ module Agents
88 90
         if match
89 91
           if keep_event?
90 92
             payload = event.payload.dup
91
-            payload['message'] = make_message(event[:payload]) if options['message'].present?
93
+            payload['message'] = interpolate_string(options['message'], event.payload) if options['message'].present?
92 94
           else
93
-            payload = { 'message' => make_message(event[:payload]) }
95
+            payload = { 'message' => interpolate_string(options['message'], event.payload) }
94 96
           end
95 97
 
96 98
           create_event :payload => payload

+ 0 - 15
db/migrate/20140426202023_migrate_hipchat_and_ef_agent_to_liquid.rb

@@ -1,15 +0,0 @@
1
-class MigrateHipchatAndEfAgentToLiquid < ActiveRecord::Migration
2
-  def up
3
-    Agent.where(:type => 'Agents::HipchatAgent').each do |agent|
4
-      LiquidMigrator.convert_all_agent_options(agent)
5
-    end
6
-    Agent.where(:type => 'Agents::EventFormattingAgent').each do |agent|
7
-      agent.options['instructions'] = LiquidMigrator.convert_hash(agent.options['instructions'], {:merge_path_attributes => true, :leading_dollarsign_is_jsonpath => true})
8
-      agent.save
9
-    end
10
-  end
11
-
12
-  def down
13
-    raise ActiveRecord::IrreversibleMigration, "Cannot revert migration to Liquid templating"
14
-  end
15
-end

+ 0 - 11
db/migrate/20140430215234_migrate_pushbullet_agent_to_liquid.rb

@@ -1,11 +0,0 @@
1
-class MigratePushbulletAgentToLiquid < ActiveRecord::Migration
2
-  def up
3
-    Agent.where(:type => 'Agents::PushbulletAgent').each do |agent|
4
-      LiquidMigrator.convert_all_agent_options(agent)
5
-    end
6
-  end
7
-
8
-  def down
9
-    raise ActiveRecord::IrreversibleMigration, "Cannot revert migration to Liquid templating"
10
-  end
11
-end

+ 0 - 11
db/migrate/20140501183219_migrate_jabber_agent_to_liquid.rb

@@ -1,11 +0,0 @@
1
-class MigrateJabberAgentToLiquid < ActiveRecord::Migration
2
-  def up
3
-    Agent.where(:type => 'Agents::JabberAgent').each do |agent|
4
-      LiquidMigrator.convert_all_agent_options(agent)
5
-    end
6
-  end
7
-
8
-  def down
9
-    raise ActiveRecord::IrreversibleMigration, "Cannot revert migration to Liquid templating"
10
-  end
11
-end

+ 0 - 11
db/migrate/20140501191849_migrate_data_output_agent_to_liquid.rb

@@ -1,11 +0,0 @@
1
-class MigrateDataOutputAgentToLiquid < ActiveRecord::Migration
2
-  def up
3
-    Agent.where(:type => 'Agents::DataOutputAgent').each do |agent|
4
-      LiquidMigrator.convert_all_agent_options(agent)
5
-    end
6
-  end
7
-
8
-  def down
9
-    raise ActiveRecord::IrreversibleMigration, "Cannot revert migration to Liquid templating"
10
-  end
11
-end

+ 0 - 12
db/migrate/20140501200859_migrate_translation_agent_to_liquid.rb

@@ -1,12 +0,0 @@
1
-class MigrateTranslationAgentToLiquid < ActiveRecord::Migration
2
-  def up
3
-    Agent.where(:type => 'Agents::TranslationAgent').each do |agent|
4
-      agent.options['content'] = LiquidMigrator.convert_hash(agent.options['content'], {:merge_path_attributes => true, :leading_dollarsign_is_jsonpath => true})
5
-      agent.save
6
-    end
7
-  end
8
-
9
-  def down
10
-    raise ActiveRecord::IrreversibleMigration, "Cannot revert migration to Liquid templating"
11
-  end
12
-end

+ 0 - 14
db/migrate/20140501203828_migrate_twitter_publish_agent_to_liquid.rb

@@ -1,14 +0,0 @@
1
-class MigrateTwitterPublishAgentToLiquid < ActiveRecord::Migration
2
-  def up
3
-    Agent.where(:type => 'Agents::TwitterPublishAgent').each do |agent|
4
-      if (message = agent.options.delete('message_path')).present?
5
-        agent.options['message'] = "{{#{message}}}"
6
-        agent.save
7
-      end
8
-    end
9
-  end
10
-
11
-  def down
12
-    raise ActiveRecord::IrreversibleMigration, "Cannot revert migration to Liquid templating"
13
-  end
14
-end

+ 45 - 0
db/migrate/20140505201716_migrate_agents_to_liquid_templating.rb

@@ -0,0 +1,45 @@
1
+class MigrateAgentsToLiquidTemplating < ActiveRecord::Migration
2
+  def up
3
+    Agent.where(:type => 'Agents::HipchatAgent').each do |agent|
4
+      LiquidMigrator.convert_all_agent_options(agent)
5
+    end
6
+    Agent.where(:type => 'Agents::EventFormattingAgent').each do |agent|
7
+      agent.options['instructions'] = LiquidMigrator.convert_hash(agent.options['instructions'], {:merge_path_attributes => true, :leading_dollarsign_is_jsonpath => true})
8
+      agent.save
9
+    end
10
+    Agent.where(:type => 'Agents::PushbulletAgent').each do |agent|
11
+      LiquidMigrator.convert_all_agent_options(agent)
12
+    end
13
+    Agent.where(:type => 'Agents::JabberAgent').each do |agent|
14
+      LiquidMigrator.convert_all_agent_options(agent)
15
+    end
16
+    Agent.where(:type => 'Agents::DataOutputAgent').each do |agent|
17
+      LiquidMigrator.convert_all_agent_options(agent)
18
+    end
19
+    Agent.where(:type => 'Agents::TranslationAgent').each do |agent|
20
+      agent.options['content'] = LiquidMigrator.convert_hash(agent.options['content'], {:merge_path_attributes => true, :leading_dollarsign_is_jsonpath => true})
21
+      agent.save
22
+    end
23
+    Agent.where(:type => 'Agents::TwitterPublishAgent').each do |agent|
24
+      if (message = agent.options.delete('message_path')).present?
25
+        agent.options['message'] = "{{#{message}}}"
26
+        agent.save
27
+      end
28
+    end
29
+    Agent.where(:type => 'Agents::TriggerAgent').each do |agent|
30
+      agent.options['message'] = LiquidMigrator.convert_make_message(agent.options['message'])
31
+      agent.save
32
+    end
33
+    Agent.where(:type => 'Agents::PeakDetectorAgent').each do |agent|
34
+      agent.options['message'] = LiquidMigrator.convert_make_message(agent.options['message'])
35
+      agent.save
36
+    end
37
+    Agent.where(:type => 'Agents::HumanTaskAgent').each do |agent|
38
+      LiquidMigrator.convert_all_agent_options(agent)
39
+    end
40
+  end
41
+
42
+  def down
43
+    raise ActiveRecord::IrreversibleMigration, "Cannot revert migration to Liquid templating"
44
+  end
45
+end

+ 11 - 1
lib/liquid_migrator.rb

@@ -22,7 +22,13 @@ module LiquidMigrator
22 22
         when 'ActiveSupport::HashWithIndifferentAccess'
23 23
           hash[key] = convert_hash(hash[key], options)
24 24
         when 'Array'
25
-          hash[key] = hash[key].collect { |k| convert_string(k, options[:leading_dollarsign_is_jsonpath])}
25
+          hash[key] = hash[key].collect { |k|
26
+            if k.class == String
27
+              convert_string(k, options[:leading_dollarsign_is_jsonpath])
28
+            else
29
+              convert_hash(k, options)
30
+            end
31
+          }
26 32
         end
27 33
       end
28 34
         # remove the unneeded *_path attributes
@@ -50,6 +56,10 @@ module LiquidMigrator
50 56
     end
51 57
   end
52 58
 
59
+  def self.convert_make_message(string)
60
+    string.gsub(/<([^>]+)>/, "{{\\1}}")
61
+  end
62
+
53 63
   def self.convert_json_path(string, filter = "")
54 64
     check_path(string)
55 65
     if string.start_with? '$.'

+ 53 - 0
spec/lib/liquid_migrator_spec.rb

@@ -56,6 +56,14 @@ describe LiquidMigrator do
56 56
     end
57 57
   end
58 58
 
59
+  describe "migrating the 'make_message' format" do
60
+    it "should work" do
61
+      LiquidMigrator.convert_make_message('<message>').should == '{{message}}'
62
+      LiquidMigrator.convert_make_message('<new.message>').should == '{{new.message}}'
63
+      LiquidMigrator.convert_make_message('Hello <world>. How is <nested.life>').should == 'Hello {{world}}. How is {{nested.life}}'
64
+    end
65
+  end
66
+
59 67
   describe "migrating an actual agent" do
60 68
     before do
61 69
       valid_params = {
@@ -99,5 +107,50 @@ describe LiquidMigrator do
99 107
       expect { LiquidMigrator.convert_all_agent_options(@agent) }.
100 108
         to raise_error("JSONPath '$.very.complex[*]' is too complex, please check your migration.")
101 109
     end
110
+
111
+    it "should work with the human task agent" do
112
+      valid_params = {
113
+        'expected_receive_period_in_days' => 2,
114
+        'trigger_on' => "event",
115
+        'hit' =>
116
+          {
117
+            'assignments' => 1,
118
+            'title' => "Sentiment evaluation",
119
+            'description' => "Please rate the sentiment of this message: '<$.message>'",
120
+            'reward' => 0.05,
121
+            'lifetime_in_seconds' => 24 * 60 * 60,
122
+            'questions' =>
123
+              [
124
+                {
125
+                  'type' => "selection",
126
+                  'key' => "sentiment",
127
+                  'name' => "Sentiment",
128
+                  'required' => "true",
129
+                  'question' => "Please select the best sentiment value:",
130
+                  'selections' =>
131
+                    [
132
+                      { 'key' => "happy", 'text' => "Happy" },
133
+                      { 'key' => "sad", 'text' => "Sad" },
134
+                      { 'key' => "neutral", 'text' => "Neutral" }
135
+                    ]
136
+                },
137
+                {
138
+                  'type' => "free_text",
139
+                  'key' => "feedback",
140
+                  'name' => "Have any feedback for us?",
141
+                  'required' => "false",
142
+                  'question' => "Feedback",
143
+                  'default' => "Type here...",
144
+                  'min_length' => "2",
145
+                  'max_length' => "2000"
146
+                }
147
+              ]
148
+          }
149
+      }
150
+      @agent = Agents::HumanTaskAgent.new(:name => "somename", :options => valid_params)
151
+      @agent.user = users(:jane)
152
+      LiquidMigrator.convert_all_agent_options(@agent)
153
+      @agent.reload.options['hit']['description'].should == "Please rate the sentiment of this message: '{{message}}'"
154
+    end
102 155
   end
103 156
 end

+ 12 - 9
spec/models/agents/human_task_agent_spec.rb

@@ -1,6 +1,9 @@
1 1
 require 'spec_helper'
2
+require 'models/concerns/liquid_interpolatable'
2 3
 
3 4
 describe Agents::HumanTaskAgent do
5
+  it_behaves_like LiquidInterpolatable
6
+
4 7
   before do
5 8
     @checker = Agents::HumanTaskAgent.new(:name => "my human task agent")
6 9
     @checker.options = @checker.default_options
@@ -116,19 +119,19 @@ describe Agents::HumanTaskAgent do
116 119
       @checker.options['poll_options'] = { 'title' => "Take a poll about jokes",
117 120
                                            'instructions' => "Rank these by how funny they are",
118 121
                                            'assignments' => 3,
119
-                                           'row_template' => "<$.joke>" }
122
+                                           'row_template' => "{{joke}}" }
120 123
       @checker.should be_valid
121 124
       @checker.options['poll_options'] = { 'instructions' => "Rank these by how funny they are",
122 125
                                            'assignments' => 3,
123
-                                           'row_template' => "<$.joke>" }
126
+                                           'row_template' => "{{joke}}" }
124 127
       @checker.should_not be_valid
125 128
       @checker.options['poll_options'] = { 'title' => "Take a poll about jokes",
126 129
                                            'assignments' => 3,
127
-                                           'row_template' => "<$.joke>" }
130
+                                           'row_template' => "{{joke}}" }
128 131
       @checker.should_not be_valid
129 132
       @checker.options['poll_options'] = { 'title' => "Take a poll about jokes",
130 133
                                            'instructions' => "Rank these by how funny they are",
131
-                                           'row_template' => "<$.joke>" }
134
+                                           'row_template' => "{{joke}}" }
132 135
       @checker.should_not be_valid
133 136
       @checker.options['poll_options'] = { 'title' => "Take a poll about jokes",
134 137
                                            'instructions' => "Rank these by how funny they are",
@@ -207,9 +210,9 @@ describe Agents::HumanTaskAgent do
207 210
 
208 211
   describe "creating hits" do
209 212
     it "can create HITs based on events, interpolating their values" do
210
-      @checker.options['hit']['title'] = "Hi <.name>"
211
-      @checker.options['hit']['description'] = "Make something for <.name>"
212
-      @checker.options['hit']['questions'][0]['name'] = "<.name> Question 1"
213
+      @checker.options['hit']['title'] = "Hi {{name}}"
214
+      @checker.options['hit']['description'] = "Make something for {{name}}"
215
+      @checker.options['hit']['questions'][0]['name'] = "{{name}} Question 1"
213 216
 
214 217
       question_form = nil
215 218
       hitInterface = OpenStruct.new
@@ -232,7 +235,7 @@ describe Agents::HumanTaskAgent do
232 235
     end
233 236
 
234 237
     it "works without an event too" do
235
-      @checker.options['hit']['title'] = "Hi <.name>"
238
+      @checker.options['hit']['title'] = "Hi {{name}}"
236 239
       hitInterface = OpenStruct.new
237 240
       hitInterface.id = 123
238 241
       mock(hitInterface).question_form(instance_of Agents::HumanTaskAgent::AgentQuestionForm)
@@ -483,7 +486,7 @@ describe Agents::HumanTaskAgent do
483 486
           'title' => "Hi!",
484 487
           'instructions' => "hello!",
485 488
           'assignments' => 2,
486
-          'row_template' => "This is <.sentiment>"
489
+          'row_template' => "This is {{sentiment}}"
487 490
         }
488 491
         @event.save!
489 492
         mock(RTurk::GetReviewableHITs).create { mock!.hit_ids { %w[JH3132836336DHG JH39AA63836DHG JH39AA63836DH12345] } }

+ 3 - 0
spec/models/agents/peak_detector_agent_spec.rb

@@ -1,6 +1,9 @@
1 1
 require 'spec_helper'
2
+require 'models/concerns/liquid_interpolatable'
2 3
 
3 4
 describe Agents::PeakDetectorAgent do
5
+  it_behaves_like LiquidInterpolatable
6
+
4 7
   before do
5 8
     @valid_params = {
6 9
         'name' => "my peak detector agent",

+ 4 - 1
spec/models/agents/trigger_agent_spec.rb

@@ -1,6 +1,9 @@
1 1
 require 'spec_helper'
2
+require 'models/concerns/liquid_interpolatable'
2 3
 
3 4
 describe Agents::TriggerAgent do
5
+  it_behaves_like LiquidInterpolatable
6
+
4 7
   before do
5 8
     @valid_params = {
6 9
       'name' => "my trigger agent",
@@ -11,7 +14,7 @@ describe Agents::TriggerAgent do
11 14
                       'value' => "a\\db",
12 15
                       'path' => "foo.bar.baz",
13 16
                     }],
14
-        'message' => "I saw '<foo.bar.baz>' from <name>"
17
+        'message' => "I saw '{{foo.bar.baz}}' from {{name}}"
15 18
       }
16 19
     }
17 20
 

+ 21 - 1
spec/models/concerns/liquid_interpolatable.rb

@@ -20,7 +20,7 @@ shared_examples_for LiquidInterpolatable do
20 20
 
21 21
   describe "interpolating liquid templates" do
22 22
     it "should work" do
23
-      @checker.send(:interpolate_options, @checker.options, @event.payload).should == {
23
+      @checker.interpolate_options(@checker.options, @event.payload).should == {
24 24
           "normal" => "just some normal text",
25 25
           "variable" => "hello",
26 26
           "text" => "Some test with an embedded hello",
@@ -28,6 +28,26 @@ shared_examples_for LiquidInterpolatable do
28 28
       }
29 29
     end
30 30
 
31
+    it "hsould work with arrays", focus: true do
32
+      @checker.options = {"value" => ["{{variable}}", "Much array", "Hey, {{hello_world}}"]}
33
+      @checker.interpolate_options(@checker.options, @event.payload).should == {
34
+        "value" => ["hello", "Much array", "Hey, Hello world"]
35
+      }
36
+    end
37
+
38
+    it "should work recursively" do
39
+      @checker.options['hash'] = {'recursive' => "{{variable}}"}
40
+      @checker.options['indifferent_hash'] = ActiveSupport::HashWithIndifferentAccess.new({'recursive' => "{{variable}}"})
41
+      @checker.interpolate_options(@checker.options, @event.payload).should == {
42
+          "normal" => "just some normal text",
43
+          "variable" => "hello",
44
+          "text" => "Some test with an embedded hello",
45
+          "escape" => "This should be Hello+world",
46
+          "hash" => {'recursive' => 'hello'},
47
+          "indifferent_hash" => {'recursive' => 'hello'},
48
+      }
49
+    end
50
+
31 51
     it "should work for strings" do
32 52
       @checker.send(:interpolate_string, "{{variable}}", @event.payload).should == "hello"
33 53
       @checker.send(:interpolate_string, "{{variable}} you", @event.payload).should == "hello you"